
#include "stdafx.h"

#include "gl/glew.h"
#include "glsw.h"
#include "technique.h"
#include "glfx.h"

#include <cassert>
#include <cstdio>
#include <string>
#include <map>

using namespace std;

static bool g_glswInitialized=false;

IProgram::IProgram(const string& name, const string& alias)
    :   m_name(name)
    ,   m_alias(alias)
{
    if(!g_glswInitialized) {
        glswInit();
        glswSetPath("shaders/", ".glsl");
        glswAddDirectiveToken("", "#version 420");
        g_glswInitialized=true;
    }
    glGenFramebuffers(1, &m_fbo);
}

IProgram::~IProgram()
{
    for(map<string, TexUnit>::const_iterator it=m_texUnits.begin(); it!=m_texUnits.end(); ++it)
        glDeleteSamplers(1, &it->second.m_sampler);
    glDeleteFramebuffers(1, &m_fbo);
    glUseProgram(0);
    glDeleteProgram(m_programId);
}

GLint IProgram::GetAttribLocation(const string& attr) const
{
    return m_attribMap.at(attr);
}

void IProgram::SetSourceTextures() const
{
    for(map<string, TexUnit>::const_iterator it=m_texUnits.begin(); it!=m_texUnits.end(); ++it)
        if(it->second.m_location>0) {
            glUniform1i(it->second.m_location, it->second.m_unit);
            glBindSampler(it->second.m_unit, it->second.m_sampler);
        }
}

void IProgram::SetTargetTexture(GLuint texId, GLenum target) const
{
    assert(target==GL_TEXTURE_2D || target==GL_TEXTURE_RECTANGLE);

    m_targetTex=texId;
    m_targetType=target;
}

void IProgram::CompileShader( GLuint shader, const string& src, bool verbose/*=true*/ )
{
    const char* tmpSrc=glswGetShader(src.c_str());
    glShaderSource(shader, 1, &tmpSrc, NULL);
    glCompileShader(shader);

    GLint tmp,res;
    glGetShaderiv(shader, GL_COMPILE_STATUS, &tmp);
    res=tmp;

    if(verbose) {
        fprintf(stderr,
            "Status: %s shader compiled with%s errors\n",
            src.c_str(), tmp ? "out" : "");
        glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &tmp);
        GLchar* infoLog=new GLchar[tmp];
        glGetShaderInfoLog(shader, tmp, &tmp, infoLog);
        fprintf(stderr,
            "Compilation details for %s shader:\n%s\n",
            src.c_str(), infoLog);
        delete[] infoLog;
    }

    assert(res!=0);
}

void IProgram::LinkProgram( GLuint programId, bool verbose/*=true*/ )
{
    glLinkProgram(programId);

    GLint tmp,res;
    glGetProgramiv(programId, GL_LINK_STATUS, &tmp);
    res=tmp;

    if(verbose) {
        fprintf(stderr, "Status: Link %s\n", res ? "successful" : "failed");
        glGetProgramiv(programId, GL_INFO_LOG_LENGTH, &tmp);
        GLchar* infoLog = new GLchar[tmp];
        glGetProgramInfoLog(programId, tmp, &tmp, infoLog);
        fprintf(stderr, "Linkage details:\n%s\n", infoLog);
        delete[] infoLog;
    }

    assert(res!=0);
}

void IProgram::AttachGlobalUniforms(const UniformMap& uBlocks)
{
    m_uBlocks.clear();

    GetActiveUniforms(uBlocks);

    for(unsigned i=0; i<m_uBlocks.size(); i++)
        m_uBlocks[i]->BindToProgram(m_programId, i);
}

void IProgram::ResetToProgram() const
{
    ResetStateToProgram();
    SetSourceTextures();

    for(unsigned i=0;i<m_uBlocks.size();i++)
        m_uBlocks[i]->BindToState(i);
}

void IProgram::SetTexture(const string& texName, GLuint texId) const
{
    map<string, TexUnit>::const_iterator it=m_texUnits.find(texName);
    assert(it!=m_texUnits.end());

    glActiveTexture(GL_TEXTURE0+it->second.m_unit);
    glBindTexture(it->second.m_target, texId);
}

PresentTechnique::PresentTechnique(const string& shaderName)
    :   IProgram(shaderName, "Present")
{
    char tmp[50];

    sprintf_s(tmp, 50, "#define MAX_LIGHTS %d", MAX_LIGHTS);
    glswAddDirectiveToken(shaderName.c_str(), tmp);

    m_programId=glCreateProgram();
    
    GLuint shader=glCreateShader(GL_VERTEX_SHADER);
    CompileShader(shader, shaderName+".VS");
    glAttachShader(m_programId, shader);
    glDeleteShader(shader);

    shader=glCreateShader(GL_FRAGMENT_SHADER);
    CompileShader(shader, shaderName+".FS");
    glAttachShader(m_programId, shader);
    glDeleteShader(shader);

    LinkProgram(m_programId);

    m_attribMap["Position"]=glGetAttribLocation(m_programId, "Position");
    m_attribMap["Normal"]=glGetAttribLocation(m_programId, "Normal");
    m_attribMap["TexCoords0"]=glGetAttribLocation(m_programId, "TexCoords");

    TexUnit tUnit;
    tUnit.m_location=glGetUniformLocation(m_programId, "TexImg");
    tUnit.m_unit=0;
    tUnit.m_target=GL_TEXTURE_2D;
    glGenSamplers(1, &tUnit.m_sampler);
    glSamplerParameteri(tUnit.m_sampler, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
    glSamplerParameteri(tUnit.m_sampler, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glSamplerParameteri(tUnit.m_sampler, GL_TEXTURE_WRAP_S, GL_MIRRORED_REPEAT);
    glSamplerParameteri(tUnit.m_sampler, GL_TEXTURE_WRAP_T, GL_MIRRORED_REPEAT);
    glSamplerParameteri(tUnit.m_sampler, GL_TEXTURE_MAX_ANISOTROPY_EXT, 4);
    m_texUnits["Image"]=tUnit;
}

PresentTechnique::PresentTechnique(const string& name, const string& alias)
    :   IProgram(name, alias)
{
}

PresentTechnique::~PresentTechnique()
{
}

void PresentTechnique::DrawPass(int pass/* =0 */) const
{
}

void PresentTechnique::SetImgTexture(GLuint tex) const
{
    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, tex);
}

void PresentTechnique::GetActiveUniforms(const UniformMap& uBlocks)
{
    m_uBlocks.push_back(uBlocks.at("Lights"));
    m_uBlocks.push_back(uBlocks.at("Matrices"));
    m_uBlocks.push_back(uBlocks.at("Model"));
}

void PresentTechnique::SetViewport(GLint x, GLint y, GLsizei width, GLsizei height)
{
    m_viewportX=x;
    m_viewportY=y;
    m_viewportWidth=width;
    m_viewportHeight=height;
}

void PresentTechnique::ResetStateToProgram() const
{
    glBindFramebuffer(GL_FRAMEBUFFER, 0);
    glEnable(GL_DEPTH_TEST);
    glEnable(GL_CULL_FACE);
    glCullFace(GL_BACK);
    glDepthFunc(GL_LEQUAL);
    glViewport(m_viewportX, m_viewportY, m_viewportWidth, m_viewportHeight);
    glUseProgram(m_programId);
}

WaveSolveTechnique::WaveSolveTechnique(GLsizei maxWidth, GLsizei maxHeight)
    :   IProgram("wave", "WaveSolve")
    ,   m_maxWidth(maxWidth)
    ,   m_maxHeight(maxHeight)
    ,   m_GaussianProgramId(m_programId)
{
    glGenTextures(1, &m_tmpTex);
    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_RECTANGLE, m_tmpTex);
    glTexParameteri(GL_TEXTURE_RECTANGLE, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_RECTANGLE, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_RECTANGLE, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
    glTexParameteri(GL_TEXTURE_RECTANGLE, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
    glTexImage2D(GL_TEXTURE_RECTANGLE, 0, GL_R32F, maxWidth, maxHeight, 0, GL_RED, GL_FLOAT, NULL);
    
    glBindFramebuffer(GL_FRAMEBUFFER, m_fbo);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_RECTANGLE, m_tmpTex, 0);

    glBindFramebuffer(GL_FRAMEBUFFER, 0);

    m_GaussianProgramId=glCreateProgram();

    GLuint shader=glCreateShader(GL_VERTEX_SHADER);
    CompileShader(shader, "wave.Gaussian.VS");
    glAttachShader(m_GaussianProgramId, shader);
    glDeleteShader(shader);

    shader=glCreateShader(GL_FRAGMENT_SHADER);
    CompileShader(shader, "wave.Gaussian.FS");
    glAttachShader(m_GaussianProgramId, shader);
    glDeleteShader(shader);

    LinkProgram(m_GaussianProgramId);

    m_texUnits["Wave"].m_location=glGetUniformLocation(m_GaussianProgramId, "TexImg");
    m_texUnits["Weights"].m_location=glGetUniformLocation(m_GaussianProgramId, "Weights");
    m_uniformDir=glGetUniformLocation(m_GaussianProgramId, "Direction");

    m_waveProgramId=glCreateProgram();
    shader=glCreateShader(GL_VERTEX_SHADER);
    CompileShader(shader, "wave.Wave.VS");
    glAttachShader(m_waveProgramId, shader);
    glDeleteShader(shader);

    shader=glCreateShader(GL_FRAGMENT_SHADER);
    CompileShader(shader, "wave.Wave.FS");
    glAttachShader(m_waveProgramId, shader);
    glDeleteShader(shader);

    LinkProgram(m_waveProgramId);

    m_WaveTexUnits.push_back(glGetUniformLocation(m_waveProgramId, "uWave"));
    m_WaveTexUnits.push_back(glGetUniformLocation(m_waveProgramId, "uPrevWave"));
    m_WaveTexUnits.push_back(glGetUniformLocation(m_waveProgramId, "uWaveStepConst"));

    glGenVertexArrays(1, &m_vao);
    glGenBuffers(1, &m_vbo);
    glBindBuffer(GL_ARRAY_BUFFER, m_vbo);

    //  2---3
    //  |\  |
    //  | \ |
    //  |  \|
    //  0---1
    const GLint loc[] = {
        0, 0,
        1, 0,
        0, 1,
        1, 1
    };
    glBufferData(GL_ARRAY_BUFFER, sizeof(loc), loc, GL_STATIC_DRAW);
    
    glBindBuffer(GL_ARRAY_BUFFER, 0);
    glBindVertexArray(0);
}

WaveSolveTechnique::~WaveSolveTechnique()
{
    glBindFramebuffer(GL_FRAMEBUFFER, m_fbo);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_RECTANGLE, 0, 0);
    glBindFramebuffer(GL_FRAMEBUFFER, 0);
    glDeleteTextures(1, &m_tmpTex);
    glDeleteBuffers(1, &m_vbo);
    glDeleteVertexArrays(1, &m_vao);
    glDeleteProgram(m_waveProgramId);
    // Gaussian is deleted by base
}

void WaveSolveTechnique::AttachGlobalUniforms(const UniformMap& uBlocks)
{}

void WaveSolveTechnique::SetTextureSize(GLsizei width, GLsizei height) const
{
    m_width=width;
    m_height=height;
}

void WaveSolveTechnique::DrawPass(int pass/* =0 */) const
{
    glBindFramebuffer(GL_FRAMEBUFFER, m_fbo);
    glViewport(0, 0, m_width, m_height);
    glDisable(GL_DEPTH_TEST);

    glUseProgram(m_GaussianProgramId);

    SetSourceTextures();

    glDrawBuffer(GL_COLOR_ATTACHMENT0);
    glUniform2i(m_uniformDir, 1, 0);
    glBindVertexArray(m_vao);
    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);

    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, m_targetType, m_targetTex, 0);
    glDrawBuffer(GL_COLOR_ATTACHMENT1);
    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_RECTANGLE, m_tmpTex);
    glUniform2i(m_uniformDir, 0, 1);
    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);

    // Detach the target texture
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, m_targetType, 0, 0);
}

DisplacedQuadTechnique::DisplacedQuadTechnique(const string& shaderName)
    :   PresentTechnique(shaderName, "DisplacedQuad")
{
    char log[5000];
    int effect=glfxCreateEffect();
    glfxEffectSource(effect, "shaders/tessQuadfx.glsl");
    m_programId=glfxCompileProgram(effect, "DisplacedQuadTextured");
    glfxGetEffectLog(effect, log, sizeof(log)-1);
    fprintf(stderr, log);

    m_attribMap["Position"]=glGetAttribLocation(m_programId, "VSIn.Position");
    m_attribMap["TexCoord"]=glGetAttribLocation(m_programId, "VSIn.TexCoords");

    TexUnit tUnit;
    tUnit.m_location=glGetUniformLocation(m_programId, "TexDisp");
    tUnit.m_unit=1;
    tUnit.m_target=GL_TEXTURE_2D;
    tUnit.m_sampler=glfxGenerateSampler(effect, "TexDisp");
    m_texUnits["Displacement"]=tUnit;

    tUnit.m_location=glGetUniformLocation(m_programId, "TexDiffuse");
    tUnit.m_unit=0;
    tUnit.m_target=GL_TEXTURE_2D;
    tUnit.m_sampler=glfxGenerateSampler(effect, "TexDiffuse");
    m_texUnits["Image"]=tUnit;

    glfxDeleteEffect(effect);
}

DisplacedQuadTechnique::~DisplacedQuadTechnique()
{}

void DisplacedQuadTechnique::GetActiveUniforms(const UniformMap& uBlocks)
{
    PresentTechnique::GetActiveUniforms(uBlocks);
    m_uBlocks.push_back(uBlocks.at("MaterialAttrs"));
}

void DisplacedQuadTechnique::SetDispTexture(GLuint tex) const
{
    glActiveTexture(GL_TEXTURE1);
    glBindTexture(GL_TEXTURE_2D, tex);
}
